/*****************************************************************************
*                     Organizacion de Datos - 75.06                          *
*                         Catedra Lic. Servetto                              *
*            Facultad de Ingenieria - Universidad de Buenos Aires            *
******************************************************************************/

#include "ConexionCapaConsulta.h"
#include "Integridad.h"

using namespace std;
using namespace Comun;



namespace CapaMetadata
{
	/*
	int stringToInt(string s){
		int i;
		stringstream ss;
		ss << s;
		ss >> i;
		return í;
	}
	*/

	void ConexionCapaConsulta::EjecutarLlamada(stringstream* entrada, stringstream* salida)
	{


		OperacionConsulta operacion = ConversorDeStreams::StreamAOperacionConsulta(entrada);

		switch(operacion)
		{
			case ClasePresente:
				
				ConexionCapaConsulta::OperacionExisteClase(entrada,salida);
				
				//TODO: Verificar que exista la clase. Devuelve un booleano como resultado, y si la clase existe,
				// devuleve la clase tambien.
				
				// Estructura del archivo de entrada:

				// ClasePresente
				// [NombreDeCLase]

				// Estructura del archivo de salida:

				// [Resultado:booleano]
				// [NombreDeClase: string] <- Solo si existe la clase.
				// [CantidadDeMiembros: entero]
				// [Miembro: Atributo]

				// Ya esta creado el metodo ClaseAStream, que sirve para generar el archivo de salida
				

				return;

			case InsertarInstancia:
////////////////////////////////
				//TODO: Insertar
				ConexionCapaConsulta::OperacionInsertar(entrada,salida);
				
////////////////////////////////

				

				// Estructura del archivo de entrada:
				
				// InsertarInstancia
				// [NombreDeCLase]
				// [CantidadDeMiembros: entero]
				// [Miembro: ClaveValor] <- Posee nombre y valor (no es necesario el tipo) de los miembros a actualizar. Existe el metodo ListaDeDatosAStream y StreamAListaDeDatos

				// Estructura del archivo de salida:

				// [Resultado: entero] <-- Ver enumerado CodigoConsultaResultado
				// [Mensaje: string] <- del tipo "Se insert� una instancia de la clase X".
				
				return;

			case BorrarInstancia:
				//TODO: Borrado
////////////////////

				ConexionCapaConsulta::OperacionBorrar(entrada,salida);

////////////////////////////////

				// Estructura del archivo de entrada:

				// BorrarInstancia
				// [NombreDeCLase]
				// [Condiciones: CondicionCompuesta] <-- Existen los metodos CondicionAStream y StreamACondicion que ya resuelven esta parte				

				// Estructura del archivo de salida:

				// [Resultado: entero] <-- Ver enumerado CodigoConsultaResultado
				// [Mensaje: string] <- del tipo "Se borraron n instancia(s) de la clase X".
				return;

			case ActualizarInstancia:
				//TODO: Actualizacion
////////////////////////////////
				ConexionCapaConsulta::OperacionActualizar(entrada,salida);
			
////////////////////////////////

				// Estructura del archivo de entrada:

				// ActualizarInstancia
				// [NombreDeCLase]
				// [CantidadDeMiembros: entero]
				// [Miembro: Atributo] <- Posee nombre y valor de los miembros a actualizar. Existe el metodo ListaDeDatosAStream y StreamAListaDeDatos
				// [Condiciones: CondicionCompuesta] <-- Existen los metodos CondicionAStream y StreamACondicion que ya resuelven esta parte				

				// Estructura del archivo de salida:

				// [Resultado: entero] <-- Ver enumerado CodigoConsultaResultado
				// [Mensaje: string] <- del tipo "Se actualizaron n instancia(s) de la clase X".

				return;

			case SeleccionarInstancia:
				//TODO: Seleccion
////////////////////

				ConexionCapaConsulta::OperacionSeleccionar(entrada,salida);

////////////////////////////////

				// Estructura del archivo de entrada:

				// SeleccionarInstancia
				// [NombreDeCLase]				
				// [MiembrosASeleccionar: list<string>] <-- Columnas a seleccionar, existe ListaDeMiembrosAStream y StreamAListaDeMiembros
				// [Condiciones: CondicionCompuesta] <-- Existen los metodos CondicionAStream y StreamACondicion que ya resuelven esta parte				

				// Estructura del archivo de salida:

				// [Resultado: entero] <-- Ver enumerado CodigoConsultaResultado
				// [Mensaje: string] <- del tipo "Se insert� una instancia de la clase X".
				// [Registros: Grupo] <-- Ac� conviene utilizar un grupo (sin atributos, solo con los registros resultado) para reaprovechar el m�todo GrupoDeDatosAStream
				
				
				return;

			case CrearClase:
				
				// TODO: crear clase
////////////////////

				ConexionCapaConsulta::OperacionAltaClase(entrada,salida);

////////////////////////////////

				// Estructura del archivo de entrada:

				// CrearClase
				// [path al archivo de creaci�n] <-- Es el path al archivo que contiene la metadata de la clase. La idea es hacer "CREATE CLASS "path"" En la capa de arriba.		

				// Estructura del archivo de salida:

				// [Resultado: booleano] <-- true si exito, false si no.
				
				return;
			
			default:
				cerr << "CapaMetadata->ConexionCapaConsulta.cpp->ConexionCapaConsulta::EjecutarLlamada(stringstream* entrada, stringstream* salida)" << endl << "Operacion no reconocida por la capa de metadata." << endl;
				throw new exception();
		}
	};

	void ConexionCapaConsulta::OperacionExisteClase(stringstream* entrada,stringstream* salida){
		
		string NombreClase = ConversorDeStreams::StreamACadena(entrada);
		stringstream* ArchIndices=ConexionCapaIndices::ArchIndicesExisteClase(NombreClase);

		ConexionCapaIndices::EjecutarLlamada(salida,ArchIndices);
	}

	void ConexionCapaConsulta::OperacionAltaClase(stringstream* entrada,stringstream* salida){
		string PathCreacionClase = ConversorDeStreams::StreamACadena(entrada);		
		stringstream* ArchIndices = ConexionCapaIndices::ArchIndicesAltaClase(PathCreacionClase);
		
		ConexionCapaIndices::EjecutarLlamada(salida,ArchIndices);

	}

	void ConexionCapaConsulta::OperacionInsertar(stringstream* entrada,stringstream* salida){
		string NombreClase = ConversorDeStreams::StreamACadena(entrada);
		stringstream* ArchIndices = ConexionCapaIndices::ArchIndicesGetMetadataClase(NombreClase);
		stringstream* salidaTemp=new stringstream;

		ConexionCapaIndices::EjecutarLlamada(salidaTemp,ArchIndices);

		MetadataClase* MC=MetadataClase::hidratarDesdeStream(salidaTemp);	
		
			list<ClaveValor>* Valores=ConversorDeStreams::StreamAListaDatos(entrada);

		list<RelacionIntegridad>& RelacIntegridad=MC->getRelacionesIntegridad();
		
		RelacionIntegridad IntegridadFallo;
		bool ResultadoIntegridad=true;
		for (list<RelacionIntegridad>::iterator it=RelacIntegridad.begin();((it!=RelacIntegridad.end())&&(ResultadoIntegridad));++it){
			RelacionIntegridad& RI=*(it);
			if (RI.getTipoIntegridad()==iAlta){
				ResultadoIntegridad=ResultadoIntegridad && Integridad::VerificarIntegridad(RI,Valores);
				if (!ResultadoIntegridad);
					IntegridadFallo=RI;
			}
		}

		ArchIndices=ConexionCapaIndices::ArchIndicesInsercion(NombreClase,Valores);
		if (ResultadoIntegridad){
			ConexionCapaIndices::EjecutarLlamada(salida,ArchIndices);
		}else{
			*salida << Comun::FalloIntegridad <<endl;
			
			*salida << "No se realizo la Insercion ya que no se cumple la Integridad Referenial de Alta con el campo " << IntegridadFallo.getNombreCampo() << " de la clase " << IntegridadFallo.getNombreClase() <<endl;
		}
	}

	void ConexionCapaConsulta::OperacionActualizar(stringstream* entrada,stringstream* salida){
		string NombreClase = ConversorDeStreams::StreamACadena(entrada);
		stringstream* ArchIndices=ConexionCapaIndices::ArchIndicesGetMetadataClase(NombreClase);
		stringstream* salidaTemp=new stringstream;

		ConexionCapaIndices::EjecutarLlamada(salidaTemp,ArchIndices);

		MetadataClase* MC=MetadataClase::hidratarDesdeStream(salidaTemp);


		string strTemp;
		getline(*entrada,strTemp);
		int CantElementos=atoi(strTemp.c_str());
		list<ClaveValor>* Valores=ConversorDeStreams::StreamAListaDatos(entrada);
		CondicionCompuesta* Condicion=ConversorDeStreams::StreamACondicion(entrada);

		list<RelacionIntegridad>& RelacIntegridad=MC->getRelacionesIntegridad();

		//seleccion valores OLD
		list<string> Campos;
		for (list<RelacionIntegridad>::iterator it=RelacIntegridad.begin();it!=RelacIntegridad.end();++it){
			RelacionIntegridad& RI=*(it);
			if (RI.getTipoIntegridad()==iBaja){ //si hay q controlar Integridad de baja
				//elijo los campos de la seleccion q me interesan
				for (list<ClaveValor>::iterator itValores=Valores->begin();itValores!=Valores->end();++itValores){
					ClaveValor& CV=*itValores;
					if (RI.getNombreCampo()==CV.Clave){ //si se va a cambiar ese atributo
						Campos.push_back(RI.getNombreCampo());
					}
				}
				
			}
		}

		bool ResultadoIntegridad=true;
		RelacionIntegridad IntegridadFallo;
		if (RelacIntegridad.size()>0){
			if (Campos.size()>0){
				stringstream * salidaSeleccion=new stringstream;
				stringstream * entradaSeleccion=ConexionCapaIndices::ArchIndicesSeleccion(MC->getClase(),Condicion,&Campos);
				ConexionCapaIndices::EjecutarLlamada(salidaSeleccion,entradaSeleccion);
				string line;
				getline(*salidaSeleccion,line); //resultado
				getline(*salidaSeleccion,line); //cantidad de reg
				getline(*salidaSeleccion,line); //path
				//cargo los registros
				string path=line;
				//ValoresOLD=ConexionCapaIndices::LeerRegistros(path,&Campos);
				ifstream* Archivo=new ifstream(path.c_str(),ios::out);
				//leo la cabecera de la consulta
				getline(*Archivo,line);
				list<Registro>* ValoresOLD=new list<Registro>;
				bool QuedanRegistros=true;
				do {
					QuedanRegistros=ConexionCapaIndices::LeerRegistrosBuffer(Archivo,ValoresOLD,&Campos);
					//verifico la integridad de alta sobre los valores q se van a ingresar y de baja sobre los valores q se perderan en la actualizacion
					for (list<RelacionIntegridad>::iterator it=RelacIntegridad.begin();((it!=RelacIntegridad.end())&&(ResultadoIntegridad));++it){
						RelacionIntegridad& RI=*(it);
						if (RI.getTipoIntegridad()==iBaja){
							//verifico la integridad de baja de los valores q se perderan en la acrualizacion
							ResultadoIntegridad=ResultadoIntegridad && Integridad::VerificarIntegridadBajaActualizacion(RI,ValoresOLD,Valores);
							if (!ResultadoIntegridad)
								IntegridadFallo=RI;
						}
					}
				}while (QuedanRegistros && ResultadoIntegridad);
				Archivo->close();
			}

			for (list<RelacionIntegridad>::iterator it=RelacIntegridad.begin();((it!=RelacIntegridad.end())&&(ResultadoIntegridad));++it){
				RelacionIntegridad& RI=*(it);
				if (RI.getTipoIntegridad()==iAlta){
					//verifico la integridad de los nuevos campos
					ResultadoIntegridad=ResultadoIntegridad && Integridad::VerificarIntegridad(RI,Valores);
					if (!ResultadoIntegridad)
						IntegridadFallo=RI;
				}
			}
		}
		//genero la actualizacion
		ArchIndices=ConexionCapaIndices::ArchIndicesActualizacion(NombreClase,Valores,Condicion);
		
		//si se cumple la integridad referencial
		if (ResultadoIntegridad){
			//ejecuto la operacion
			ConexionCapaIndices::EjecutarLlamada(salida,ArchIndices);
		}else{
			*salida << Comun::FalloIntegridad <<endl;
			std::string Tipo;
			if (IntegridadFallo.getTipoIntegridad()==iAlta){
				Tipo="Alta";
			}else{
				Tipo="Baja";
			}
			*salida << "No se realizo la Actualizacion ya que no se cumple la Integridad Referenial de "<< Tipo<<" con el campo " << IntegridadFallo.getNombreCampo() << " de la clase " << IntegridadFallo.getNombreClase() <<endl;
		}
	}

	void ConexionCapaConsulta::OperacionBorrar(stringstream* entrada,stringstream* salida){
		string NombreClase = ConversorDeStreams::StreamACadena(entrada);
		stringstream* ArchIndices=ConexionCapaIndices::ArchIndicesGetMetadataClase(NombreClase);

		stringstream* salidaTemp=new stringstream;

		ConexionCapaIndices::EjecutarLlamada(salidaTemp,ArchIndices);

		MetadataClase* MC=MetadataClase::hidratarDesdeStream(salidaTemp);

		CondicionCompuesta* Condicion=ConversorDeStreams::StreamACondicion(entrada);

		list<RelacionIntegridad>& RelacIntegridad=MC->getRelacionesIntegridad();


		
		//seleccion valores OLD
		list<string> Campos;
		for (list<RelacionIntegridad>::iterator it=RelacIntegridad.begin();it!=RelacIntegridad.end();++it){
			RelacionIntegridad& RI=*(it);
			if (RI.getTipoIntegridad()==iBaja){ //si hay q controlar Integridad de baja
				//elijo los campos de la seleccion q me interesan
				Campos.push_back(RI.getNombreCampo());
			}
		}
		RelacionIntegridad IntegridadFallo;
		bool ResultadoIntegridad=true;
		if (RelacIntegridad.size()>0){
			if (Campos.size()>0){
				stringstream * salidaSeleccion=new stringstream;
				stringstream * entradaSeleccion=ConexionCapaIndices::ArchIndicesSeleccion(MC->getClase(),Condicion,&Campos);
				ConexionCapaIndices::EjecutarLlamada(salidaSeleccion,entradaSeleccion);
				string line;
				getline(*salidaSeleccion,line); //resultado
				getline(*salidaSeleccion,line); //cantidad de reg
				getline(*salidaSeleccion,line); //path

				string path=line;
				//ValoresOLD=ConexionCapaIndices::LeerRegistros(path,&Campos);
				ifstream* Archivo=new ifstream(path.c_str(),ios::out);
				//leo la cabecera de la consulta
				getline(*Archivo,line);
				list<Registro>* ValoresOLD=new list<Registro>;
				bool QuedanRegistros=true;
				do {
					QuedanRegistros=ConexionCapaIndices::LeerRegistrosBuffer(Archivo,ValoresOLD,&Campos);
					//verifico la integridad de alta sobre los valores q se van a ingresar y de baja sobre los valores q se perderan en la actualizacion
					for (list<RelacionIntegridad>::iterator it=RelacIntegridad.begin();((it!=RelacIntegridad.end())&&(ResultadoIntegridad));++it){
						RelacionIntegridad& RI=*(it);
						if (RI.getTipoIntegridad()==iBaja){
							//verifico la integridad de baja de los valores q se perderan en la acrualizacion
							ResultadoIntegridad=ResultadoIntegridad && Integridad::VerificarIntegridadBajaBorrado(RI,ValoresOLD);
							if (!ResultadoIntegridad)
								IntegridadFallo=RI;
						}
					}
				}while (QuedanRegistros && ResultadoIntegridad);
				Archivo->close();
			}
		}

		ArchIndices=ConexionCapaIndices::ArchIndicesBorrado(NombreClase,Condicion);
		
		//si se cumple la integridad referencial
		if (ResultadoIntegridad){
			//ejecuto la operacion
			ConexionCapaIndices::EjecutarLlamada(salida,ArchIndices);
		}else{
			*salida << Comun::FalloIntegridad <<endl;
			*salida << "No se realizo el Borrado ya que no se cumple la Integridad Referenial de Baja con el campo " << IntegridadFallo.getNombreCampo() << " de la clase " << IntegridadFallo.getNombreClase() <<endl;
		}
	}

	void ConexionCapaConsulta::OperacionSeleccionar(stringstream* entrada,stringstream* salida){
		
		string NombreClase = ConversorDeStreams::StreamACadena(entrada);
		
		stringstream* ArchIndices;		
		ArchIndices=ConexionCapaIndices::ArchIndicesGetMetadataClase(NombreClase);
		
		stringstream* salidaTemp=new stringstream;

		ConexionCapaIndices::EjecutarLlamada(salidaTemp,ArchIndices);

		MetadataClase* MC=MetadataClase::hidratarDesdeStream(salidaTemp);

		list<string>* Campos=ConversorDeStreams::StreamAListaDeMiembros(entrada);
		CondicionCompuesta* Condicion=ConversorDeStreams::StreamACondicion(entrada);

		ArchIndices=ConexionCapaIndices::ArchIndicesSeleccion(NombreClase,Condicion,Campos);
		
		salidaTemp=new stringstream;
		ConexionCapaIndices::EjecutarLlamada(salidaTemp,ArchIndices);
		
		string str;
		getline(*salidaTemp,str);
		*salida << str << endl;
		getline(*salidaTemp,str);
		*salida << "Se seleccionaron " << str << " Registros" << endl;
		getline(*salidaTemp,str);
		*salida << str << endl;
		
	}

}
